package genome;

import static genome.ParametersRandomGen.*;

import java.util.LinkedList;
import java.util.regex.Pattern;


import exceptions.FunctionExpressionException;

import polylegs.Node;
import random.RandomDistrib;

//  une fonction est une variable resultat de l'operation de plsuieurs autres a variables
//: constantes, gradients et obstacles

// sa forme est operateur1 (operateurGauche variable1 variable2) (operateurDroit variable3 variable4)
// forme infixe

// elle peut etre utilisée dans les conditions des regles,
//son resultat, un entier sera compare à uen autre valeur

// possibles variables  : topgradient 0, botGradient 1, rightgradient 2, left graduent 3
// topsubgradient 4 , botsubgradient 5, rightsubgradient 6, leftsubgradient 7
// possible operators : Id 0, + 1, - 2, * 3, / 4, modulo 5

public class Function {

	private int op1;
	private int opg;
	private int v1g;
	private int v2g;
	private int opd;
	private int v1d;
	private int v2d;

	public Function(LinkedList<Constant> constants) {
		int nb_cst = constants.size();
		
		opg = Math.random()< proba_id_operator ? 0 : getRandomFunctionperator();
		op1 = Math.random()< proba_id_operator ? 0 : getRandomFunctionperator();
		opd = Math.random()< proba_id_operator ? 0 : getRandomFunctionperator();
		

		v1g = getRandomFunctionVariable(nb_cst);
		v2g = getRandomFunctionVariable(nb_cst);
		v1d = getRandomFunctionVariable(nb_cst);
		v2d = getRandomFunctionVariable(nb_cst);

	}

	public void mutateFunction(LinkedList<Constant> constants) {
		
		// 5 mutations are possible, a mutation is a replacement of the current value by a new value
		// given by the properties of construction of a random genome
		int nb_cst = constants.size();

		if (Math.random()
				* number_of_possible_mutations_per_function < average_number_of_mutations_per_function) {
			opg = Math.random()< proba_id_operator ? 0 : getRandomFunctionperator();
			op1 = Math.random()< proba_id_operator ? 0 : getRandomFunctionperator();
			opd = Math.random()< proba_id_operator ? 0 : getRandomFunctionperator();
		}
		if (Math.random()
				* number_of_possible_mutations_per_function < average_number_of_mutations_per_function) {
			v1g = getRandomFunctionVariable(nb_cst);
		}
		if (Math.random()
				* number_of_possible_mutations_per_function < average_number_of_mutations_per_function) {
			v2g = getRandomFunctionVariable(nb_cst);
		}
		if (Math.random()
				* number_of_possible_mutations_per_function < average_number_of_mutations_per_function) {
			v1d = getRandomFunctionVariable(nb_cst);

		}
		if (Math.random()
				* number_of_possible_mutations_per_function < average_number_of_mutations_per_function) {
			v2d = getRandomFunctionVariable(nb_cst);
		}

	}

	public Function(String currentLine) {
		if (currentLine.startsWith("*")) {
			try {
				parseText(currentLine);
			} catch (FunctionExpressionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			parseNumbers(currentLine);

		}

	}

	private void parseText(String currentLine)
			throws FunctionExpressionException {
		Pattern p = Pattern.compile(" ");
		String[] result = p.split(currentLine);

		v1g = VARfromString(result[1]);
		opg = OPfromString(result[2]);
		v2g = VARfromString(result[3]);

		// on regarde si la fonction ets complexe ou pas ie contient l'operateur
		// identite
		// on n'attend pas que l'on entre id comme operateur, on intuite que la
		// seconde partie
		// est manquante

		if (result.length > 10) {
			op1 = OPfromString(result[5]);
			v1d = VARfromString(result[7]);
			opd = OPfromString(result[8]);
			v2d = VARfromString(result[9]);

		} else {
			op1 = 0;
			opd = 0;
			v1d = 0;
			v2d = 0;
		}

	}

	private void parseNumbers(String currentLine) {
		Pattern p = Pattern.compile(" ");
		String[] result = p.split(currentLine, 8);
		op1 = Integer.parseInt(result[0]);
		opg = Integer.parseInt(result[1]);
		v1g = Integer.parseInt(result[2]);
		v2g = Integer.parseInt(result[3]);
		opd = Integer.parseInt(result[4]);
		v1d = Integer.parseInt(result[5]);
		v2d = Integer.parseInt(result[6]);
	}

	public String toString() {
		String result = "";

		result += op1 + " ";
		result += opg + " ";
		result += v1g + " ";
		result += v2g + " ";
		result += opd + " ";
		result += v1d + " ";
		result += v2d + " ";

		String resg = "( " + OPString(opg, VARString(v1g), VARString(v2g))
				+ ") ";
		String resd = "( " + OPString(opd, VARString(v1d), VARString(v2d))
				+ ") ";
		String res = OPString(op1, resg, resd);

		result += "// " + res;

		return result + "\n";

	}

	public int computeFunction(Node n, LinkedList<Constant> constants)
			throws FunctionExpressionException {

		int v1gauche = VAR(v1g, n, constants);
		int v2gauche = VAR(v2g, n, constants);
		int v1droit = VAR(v1d, n, constants);
		int v2droit = VAR(v2d, n, constants);

		int gauche = OP(opg, v1gauche, v2gauche);
		int droit = OP(opd, v1droit, v2droit);
		// System.out.println(OP(op1,gauche,droit));
		return OP(op1, gauche, droit);

	}

	private int VAR(int v, Node n, LinkedList<Constant> constants) {
		switch (v) {
		case 0:
			return n.topG;
		case 1:
			return n.botG;
		case 2:
			return n.rightG;
		case 3:
			return n.leftG;
		case 4:
			return n.topSubG;
		case 5:
			return n.botSubG;
		case 6:
			return n.rightSubG;
		case 7:
			return n.leftSubG;
		default:
			int nb_cst = constants.size();
			return constants.get((v - 8) % nb_cst).getValue();
			// si on veut utiliser uen constante dont l'index ets superieur au
			// nombre de constantes,
			// alors on considere que les constantes sont cycliques : la p eme
			// est aussi la 0 eme.
		}
	}

	private int OP(int op, int v1, int v2) throws FunctionExpressionException {
		switch (op) {
		case 0:
			return v1; // Identite
		case 1:
			return v1 + v2; // sommme
		case 2:
			return v1 - v2; // difference
		case 3:
			return v1 * v2; // Produit
		case 4:
			if (v2 == 0) {
				// return Integer.MAX_VALUE;
				throw new FunctionExpressionException(toString());
			} else {
				return v1 / v2; // Division
			}
		case 5:
			if (v2 == 0) {
				// return Integer.MAX_VALUE;
				throw new FunctionExpressionException(toString());
			} else {
				return v1 % v2; // Modulo
			}
		default:
			throw new FunctionExpressionException(toString());
		}
	}

	private String VARString(int v) {
		switch (v) {
		case 0:
			return "TopGradient ";
		case 1:
			return "BotGradient ";
		case 2:
			return "RightGradient ";
		case 3:
			return "LeftGradient ";
		case 4:
			return "TopSubGradient ";
		case 5:
			return "BotSubGradient ";
		case 6:
			return "RightSubGradient ";
		case 7:
			return "LeftSubGradient ";
		default:
			return "Constant" + (v - 8) + " ";
		}
	}

	private int VARfromString(String var) throws FunctionExpressionException {
		if (var.equals("TopGradient")) {
			return 0;
		}
		if (var.equals("BotGradient")) {
			return 1;
		}
		if (var.equals("RightGradient")) {
			return 2;
		}
		if (var.equals("LeftGradient")) {
			return 3;
		}
		if (var.equals("TopSubGradient")) {
			return 4;
		}
		if (var.equals("BotSubGradient")) {
			return 5;
		}
		if (var.equals("RightSubGradient")) {
			return 6;
		}
		if (var.equals("LeftSubGradient")) {
			return 7;
		}
		if (var.contains("Constant")) {
			return 8 + Integer.parseInt(var.replace("Constant", ""));
		}
		throw new FunctionExpressionException("Variable :" + var + ": unknown");
	}

	private String OPString(int op, String v1, String v2) {
		switch (op) {
		case 0:
			return v1; // Identite
		case 1:
			return v1 + "+ " + v2; // sommme
		case 2:
			return v1 + "- " + v2; // difference
		case 3:
			return v1 + "x " + v2; // Produit
		case 4:
			return v1 + "/ " + v2; // Division
		case 5:
			return v1 + "modulo " + v2; // Modulo
		}
		return "";
	}

	private int OPfromString(String op) throws FunctionExpressionException {
		if (op.equals("Id")) {
			return 0;
		}
		if (op.equals("+")) {
			return 1;
		}
		if (op.equals("-")) {
			return 2;
		}
		if (op.equals("x")) {
			return 3;
		}
		if (op.equals("/")) {
			return 4;
		}
		if (op.equals("modulo")) {
			return 5;
		}
		throw new FunctionExpressionException("OP " + op + " unknown");
	}

	private int getRandomFunctionperator() {
		//the 0th operator is Id, the others follow some probability distribution
		return 1 + RandomDistrib
				.resultat(proba_function_operators);

	}

	/*private int getRandomFunctionVariable(int nb_cst) {
		if ((nb_cst > 0)
				&& (Math.random() < ParametersRandomGen.proba_function_using_constant)) {
			return 8 + (int) (Math.random() * nb_cst);
		}
		return RandomDistrib
				.resultat(ParametersRandomGen.proba_function_variables);

	}*/
	
	private int getRandomFunctionVariable(int nb_cst) {
		//every variable has teh same probability, constants and gradients
		return (int)(Math.random() * (8+nb_cst));
		
		
		
		

	}
	
	
}
